package com.haredot.controller;

import cn.hutool.captcha.AbstractCaptcha;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectRequest;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haredot.constraint.RedisConst;
import com.haredot.dto.ResourceSolrDTO;
import com.haredot.entity.Comments;
import com.haredot.entity.Resource;
import com.haredot.entity.ResourceType;
import com.haredot.holder.TokenContextHolder;
import com.haredot.oss.properties.OssProperties;
import com.haredot.service.ResourceService;
import com.haredot.utils.RSA;
import com.haredot.validation.Insert;
import com.haredot.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.solr.core.query.result.HighlightEntry;
import org.springframework.data.solr.core.query.result.HighlightPage;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

@RestController
@RequestMapping("/res")
@EnableConfigurationProperties(OssProperties.class)
public class ResourceController {

    private ResourceService resourceService;

    private OssProperties ossProperties;

    private OSS ossClient;

    @javax.annotation.Resource
    private RSA rsa;

    @javax.annotation.Resource(name = "redisTemplate")
    private RedisTemplate<String, ResourceViewVo> template;


    @javax.annotation.Resource(name = "redisJdkTemplate")
    private RedisTemplate<String, AbstractCaptcha> redisTemplate;

    @Autowired
    public ResourceController(ResourceService resourceService, OssProperties ossProperties, OSS ossClient) {
        this.resourceService = resourceService;
        this.ossProperties = ossProperties;
        this.ossClient = ossClient;
    }

    @PostMapping("/upload")
    public R<?> upload(@RequestPart("resource") MultipartFile part) throws Exception {
        // 获取 要存储在 磁盘上的文件名
        String filename = "resource/" + UUID.randomUUID() + part.getOriginalFilename();
        // 构建 PutObjectRequest对象
        PutObjectRequest putObjectRequest = new PutObjectRequest(ossProperties.getBucketName(), filename, part.getInputStream());
        // 上传文件到网盘
        ossClient.putObject(putObjectRequest);
        // 返回结果
        return R.ok().data(filename);
    }

    @GetMapping("/types")
    public R<ResourceType> typeLists() {
        return resourceService.queryResourceTypeLists();
    }

    @PostMapping("/add")
    public R<?> saveResource(@RequestBody @Validated(Insert.class) Resource resource) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // 获取验证码
        String code = resource.getCaptchaCode();
        // 从 Redis 中 获取验证码
        AbstractCaptcha captcha = redisTemplate.opsForValue().get("captcha::code::" + authentication.getName());

        // 比较用户输入的验证码 是否正确
        if (captcha != null && captcha.verify(code)) {
            // 删除验证码
            redisTemplate.delete("captcha::code::" + authentication.getName());
            Long userId = TokenContextHolder.getCurrentUserId();
            resource.setUserId(userId);
            return resourceService.saveResource(resource);
        }
        // 如果验证码不正确, 直接返回，并提示验证码不正确
        return R.fail(R.StatusCode.BAD, "验证码不正确").build();
    }

    @GetMapping("/detail/{resId}")
    public R<ResourceDetailVo> resourceDetails(@PathVariable("resId") Long resId) {
        return resourceService.queryDetailsData(resId);
    }

    /**
     * 分页查询资源评论信息
     *
     * @param resId
     * @return
     */
    @GetMapping("/comments/{resId}")
    public R<Page<? extends Comments>> resourceComments(@PathVariable("resId") Long resId,
                                                        @RequestParam(value = "page", required = false, defaultValue = "1") Long current,
                                                        @RequestParam(value = "rows", required = false, defaultValue = "10") Long rows) {

        Page<? extends Comments> page = new Page<>(current, rows);

        return resourceService.resourceComments(page, resId);
    }

    /**
     * 全文检索
     *
     * @param word
     */
    @GetMapping(value = "/q", produces = MediaType.APPLICATION_JSON_VALUE)
    public R<?> fullSearchText(@RequestParam(value = "wd", required = false, defaultValue = "*") String word,
                               @RequestParam(value = "page", required = false, defaultValue = "1") Integer current,
                               @RequestParam(value = "rows", required = false, defaultValue = "10") Integer rows) {


        HighlightPage<ResourceSolrDTO> highlightPageR = resourceService.fullSearchResource(current - 1, rows, word);

        long totalElements = highlightPageR.getTotalElements();

        List<? extends ResourceSolrDTO> content = null;
        if ("*".equals(word)) {
            content = highlightPageR.getContent();
        } else {
            List<HighlightEntry<ResourceSolrDTO>> highlightedV = highlightPageR.getHighlighted();
            content = highlightedV.stream().map(highlighted -> {
                ResourceSolrVo resourceSolrVo = new ResourceSolrVo();

                ResourceSolrDTO entity = highlighted.getEntity();
                BeanUtils.copyProperties(entity, resourceSolrVo);
                // 获取高亮信息
                List<HighlightEntry.Highlight> highlights = highlighted.getHighlights();

                if (!highlights.isEmpty()) {
                    HighlightEntry.Highlight highlight = highlights.get(0);
                    List<String> snipplets = highlight.getSnipplets();
                    if (!snipplets.isEmpty()) {
                        resourceSolrVo.setHighlight(snipplets.get(0));
                    }
                }
                return resourceSolrVo;
            }).toList();
        }
        // 查询资源的访问量
        List<Map<String, Object>> maps = content.stream().map(resourceSolrDTO -> {
            Map<String, Object> map = new HashMap<>();

            ResourceViewVo resourceViewVo = new ResourceViewVo();
            BeanUtils.copyProperties(resourceSolrDTO, resourceViewVo);
            // 根据 redis 查询对应的访问量
            Double score = template.opsForZSet().score(RedisConst.RESOURCE_VISIT_RANK_SET, resourceViewVo);
            //
            map.put("score", score);
            map.put("value", resourceSolrDTO);
            return map;
        }).toList();

        return R.ok().data(Map.of("list", maps, "total", totalElements));
    }

    /**
     * 根据资源ID ,查询对用数据
     *
     * @param typeId
     */
    @GetMapping("/{typeId:\\d+}")
    public R<Map<String, Object>> queryResourceByPage(@PathVariable("typeId") Long typeId,
                                                      @RequestParam(value = "page", required = false, defaultValue = "1") Integer current,
                                                      @RequestParam(value = "rows", required = false, defaultValue = "10") Integer rows) {

        Page<Page<Map<String, Object>>> page = new Page(current, rows);

        return resourceService.queryResourcesByPage(page, typeId);

    }

    @GetMapping("/more/{uid}")
    public R<Map<String, Object>> queryMoreResource(@PathVariable("uid") Long uid,
                                                    @RequestParam(value = "page", required = false, defaultValue = "1") Integer current,
                                                    @RequestParam(value = "rows", required = false, defaultValue = "10") Integer rows) {
        Page<Page<ResourceVo>> page = new Page(current, rows);

        return resourceService.queryMoreResourceByUid(page, uid);
    }

    /**
     * 检查下载权限
     *
     * @return
     */
    @PostMapping("/checkPermission/{resId}")
    public R<?> checkPermission(@PathVariable("resId") Long resId) {
        R<Map<String, Integer>> mapR = resourceService.checkDownLoadPermission(resId);
        // 获取结果
        if (mapR.getStatus()) {
            Map<String, Integer> data = mapR.getData();
            StringJoiner joiner = new StringJoiner("&");
            // 对参数进行签名
            for (Map.Entry<String, Integer> entry : data.entrySet()) {
                joiner.add(entry.getKey() + "=" + entry.getValue());
            }
            String sign = rsa.sign(joiner.toString());
            HashMap<String, Object> stringObjectHashMap = new HashMap<>(data);
            stringObjectHashMap.put("sign", URLEncoder.encode(sign, StandardCharsets.UTF_8));
            return R.ok().data(stringObjectHashMap);
        }
        return mapR;
    }

    @GetMapping("/download")
    public HttpEntity<StreamingResponseBody> downloadResource(@RequestParam("path") String path,
                                                              @RequestParam("d") Integer divide,
                                                              @RequestParam("s") Integer score,
                                                              @RequestParam("r") Integer record,
                                                              @RequestParam("sign") String sign) {
        // 先进行 RSA 验签
        Map<String, Integer> params = Map.of("d", divide, "s", score, "r" , record);
        StringJoiner joiner = new StringJoiner("&");
        // 对参数进行签名
        for (Map.Entry<String, Integer> entry : params.entrySet()) {
            joiner.add(entry.getKey() + "=" + entry.getValue());
        }
        // 验证签名通过，进行下载、失败则不做任何处理
        if (rsa.verify(joiner.toString(), sign)) {
            // 获取要下载的文件名，并进行 转码，防止中文乱码问题。
            String filename = URLEncoder.encode(path.substring(path.lastIndexOf("/") + 37), StandardCharsets.UTF_8);

            OSSObject object = ossClient.getObject(new GetObjectRequest(ossProperties.getBucketName(), path));
            InputStream objectContent = object.getObjectContent();

            StreamingResponseBody responseBody = outputstream -> {
                 byte[] bytes = new byte[1024 * 8] ;
                 int len;
                 while((len = objectContent.read(bytes)) != -1) {
                     outputstream.write(bytes, 0 , len);
                 }
                 if (objectContent!=null) {
                     objectContent.close();
                 }
            } ;
            return ResponseEntity.ok()
                    .header("Content-Disposition", "attachment;filename=" + filename)
                    .body(responseBody);
        }

        return null ;
    }

    /**
     * 收藏 / 取消收藏
     *
     * @param resId
     * @return
     */
    @PostMapping("/collect/{resId}")
    public R<?> collectResource(@PathVariable("resId") Long resId) {
        return resourceService.collectResource(resId);
    }
}
